#include "qxwz_socket.h"
#include "qxwz_rtos_al.h"
//#include "qxwz_sdk_common.h"
//#include "console_uart.h"


#ifdef errno
#undef errno
#endif
#define errno fibo_geterrno()


static void socket_check_read(QxwzSocketStruct *handle);
static void socket_check_connect(QxwzSocketStruct *handle);

#define TCP 0
#define UDP 1

#define BigLittleSwap16(A)  ((((unsigned short)(A) & 0xff00) >> 8) | (((unsigned short)(A) & 0x00ff) << 8))


static int checkCPUendian()
{
       union{
              unsigned long int i;
              unsigned char s[4];
       }c;

       c.i = 0x12345678;
       return (0x12 == c.s[0]);
}

unsigned short qxwz_htons(unsigned short value)
{
    return checkCPUendian() ? value : BigLittleSwap16(value);
}


/*
 * @brief: open a socket,  init QxwzSocketStruct
 * @param[in] family: specifies a communication domain
 * @param[in] type: specifies the communication semantics
 * @param[in] protocol: specifies a particular protocol to be used with the socket
 * @param[out] handle: output QxwzSocketStruct handle
 *
 * @return:
 *  =0 if success
 *  -1 if fail for any reason
 */
int qxwz_sock_open(QxwzSocketStruct *handle)
{
    int fd;

    handle->nfsm = SOCKET_DISCONNECT;
    handle->rfsm = SOCKET_READSTOP;

    if ((fd = fibo_sock_create(TCP)) < 0) {
        QXLOGI("qxwz_sock_open socket error\n");
        handle->fd = -1;
        handle->nfsm = SOCKET_DISCONNECT;
        handle->rfsm = SOCKET_READSTOP;
    }

    handle->nfsm = SOCKET_DISCONNECT;
    handle->rfsm = SOCKET_READSTOP;
    handle->conn_status = -1;
    handle->read_status = -1;
    handle->fd = fd;
	QXLOGI("qxwz_sock_open socket ok \n");

    return (handle->fd > 0) ? 0 : -1;
}

/*
 * @brief: close a socket
 * @param[in] handle: a socket handle
 *
 * @return: none
 */
void qxwz_sock_close(QxwzSocketStruct *handle)
{
	QXLOGI("qxwz_sock_close enter: %d\n", handle->fd);

    if (handle->fd) {
        fibo_sock_close(handle->fd);
        QXLOGI("qxwz_sock_close: %d\n", handle->fd);
        handle->fd = -1;
        handle->conn_status = -1;
        handle->read_status = -1;
        handle->nfsm = SOCKET_DISCONNECT;
        handle->rfsm = SOCKET_READSTOP;
    }
}

/*
 * @brief: write the data to socket
 * @param[in] handle: a socket handle
 * @param[in] vptr: the buffer will be written
 * @param[in] n:  n bytes will be written to the file descriptor
 *
 * @return:
 *  >=0 if success, the number of items written.
 *  -1 if fail for any reason
 */
size_t qxwz_sock_write(QxwzSocketStruct *handle, const void *vptr, size_t n)
{
    size_t nleft;
    int nwritten;
    const char *ptr;
	QXLOGI("qxwz_sock_write enter: %d\n", handle->fd);

    if (handle->nfsm != SOCKET_CONNECTED && handle->nfsm != SOCKET_CONNECTED2)
        return -1;


    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        nwritten = fibo_sock_send(handle->fd, ptr, nleft);  
        if (nwritten < 0) {
			return (-1); /* error */
        } else {
            nleft -= nwritten;
        	ptr += nwritten;
        }        
    }

	QXLOGI("qxwz_sock_write return: %d\n", handle->fd);
    return (n);
}

/*
 * @brief: check read status
 * @param[in] handle: a socket pointer
 *
 * @return: none
 *  =0 if success
 *  -1 if fail for any reason
 *  1 if in progress
 */
static void socket_check_read(QxwzSocketStruct *handle)
{
    int ret;
    struct timeval tm;

    fd_set readfds;

	QXLOGI("socket_check_read enter: %d\n", handle->fd);

    handle->read_status = -1;

    FD_ZERO(&readfds);
    FD_SET(handle->fd, &readfds);
    handle->rfsm = SOCKET_READING;

    tm.tv_sec = handle->milsec / 1000;
    tm.tv_usec = (handle->milsec % 1000) * 1000;
    ret = fibo_sock_lwip_select(handle->fd + 1, &readfds, NULL, NULL, &tm);
    if (ret < 0) {
        QXLOGI("%s, error\n", __func__);
        handle->read_status = -1;
    } else if (ret == 0) {
        QXLOGI("%s, timeout\n", __func__);
        handle->read_status = 1;
    } else {
        QXLOGI("%s, > 0\n", __func__);

    	if (FD_ISSET(handle->fd, &readfds)) {
       		 QXLOGI("%s, readed\n", __func__);
        	handle->rfsm = SOCKET_READED;
        	handle->read_status = 0;
    	}
    }
	QXLOGI("socket_check_read return \n");
}

/*
 * @brief: read the data from socket , support block or no-block operation
 * @param[in] handle: a socket handle
 * @param[in] vptr: the buffer will be read
 * @param[in] n: attempts to read up to n bytes from file descriptor
 *
 * @return:
 *  > 0 if success, the number of items read.
 *  = 0 if the server is closed.
 *  -1 if fail for any reason
 */
size_t qxwz_sock_read(QxwzSocketStruct *handle, void *vptr, size_t n)
{
    size_t nleft;
    int nread;
    unsigned char *ptr;

    ptr = vptr;
    nleft = n;
    unsigned char *p = ptr;
	size_t temp_len = nleft;
	size_t data_len = 0;
	QXLOGI("qxwz_sock_read enter fd=%d\n",handle->fd);
	
    while (nleft > 0) {
		QXLOGI("qxwz_sock_read loop \n");
		
		if(nleft>2048) {
			temp_len = 2048;
			nleft -= 2048;

		}
		
        if ((nread = fibo_sock_recv(handle->fd, ptr, temp_len)) < 0) {
			
			if((EAGAIN == errno) || (EWOULDBLOCK == errno) ) {
				QXLOGI("qxwz_sock_read read complete \n");
				p[data_len] = 0;
				break;
					//read complete
			} else {
				QXLOGI("qxwz_sock_read error %d\n",nread);
            	return (-1);
			}

        } else if(nread==0){
        	QXLOGI("qxwz_sock_read readn 0 \n");
        
			p[data_len] = 0;
			break;

        } else {
			
			data_len += nread;
			if(nleft == 0) {
            	p[data_len] = 0;
			}
			QXLOGI("qxwz_sock_read readn len: %d msg=%s\n", nread,p);
        }

        //nleft -= nread;
        ptr += nread;

    }
	QXLOGI("qxwz_sock_read %d return \n",data_len);
	
    return (data_len); /* return >= 0 */
}

/*
 * @brief: check connect status
 * @param[in] handle: a socket pointer
 *
 * @return: none
 *  =0 if success
 *  -1 if fail for any reason
 *  1 if in progress
 */
static void socket_check_connect(QxwzSocketStruct *handle)
{
    int ret;
    struct timeval tm;

    fd_set writefds;
    int error_local, len = sizeof(int);

    handle->conn_status = -1;

    handle->nfsm = SOCKET_CONNECTING;

    FD_ZERO(&writefds);
    FD_SET(handle->fd, &writefds);

    tm.tv_sec = handle->milsec / 1000;
    tm.tv_usec = (handle->milsec % 1000) * 1000;
	QXLOGI("socket_check_connect msec=%d handle->fd=%d\n", handle->milsec, handle->fd);
    ret = fibo_sock_lwip_select(handle->fd + 1, &writefds, &writefds, NULL, &tm);
    if (ret < 0) {
        handle->conn_status = -1;
        handle->nfsm = SOCKET_CONNECTERR;
		QXLOGI("socket_check_connect error [%d] %s\n", handle->fd, strerror(errno));
    } else if (ret == 0) {
        handle->conn_status = 1;
		QXLOGI("socket_check_connect test [%d]\n", handle->fd);
    } else {

        fibo_sock_getOpt(handle->fd, SOL_SOCKET, SO_ERROR, &error_local, &len);
        if (error_local == 0) {
            handle->conn_status = 0;
            handle->nfsm = SOCKET_CONNECTED;
            QXLOGI("socket_check_connect connect ok[%d]\n", handle->fd);
        } else {
            QXLOGE("socket_check_connect socket connect error 1 = %s", strerror(errno));
            handle->conn_status = 1;
        }

    }
}


/*
 * @brief: connect socket , support block or no-block operation
 * @param[in] handle: a socket pointer
 *
 * @return: none
 *  =0 if success
 *  -1 if fail for any reason
 *  1 if in progress
 */

int qxwz_sock_connect(QxwzSocketStruct *handle)
{

    unsigned long ul = 1;
    GAPP_TCPIP_ADDR_T serv_addr;
    struct timeval tm;
	ip_addr_t addr_dns;
	
    int result;

	memset(&serv_addr,0,sizeof(serv_addr));
	memset(&addr_dns,0,sizeof(addr_dns));

	result = fibo_getHostByName(handle->host,&addr_dns,1,0);
	//QXLOGI("qxwz_sock_connect result=%d\n", result);
	if (result < 0) {
		QXLOGE("qxwz_sock_connect fail result=%d\n", result);
        handle->conn_status = -1;
        return handle->conn_status;
    }
	//QXLOGI("qxwz_sock_connect host=%s.port=%d\n", handle->host,handle->port);
	serv_addr.sin_port = qxwz_htons(handle->port);//16415;//
	serv_addr.sin_addr.u_addr.ip4.addr = addr_dns.u_addr.ip4.addr;
	//QXLOGI("qxwz_sock_connect port=%d\n", serv_addr.sin_port );


    if (handle->type == SOCKET_TYPE_BLOCK) {
		//QXLOGI("qxwz_sock_connect block \n");
        if (handle->nfsm == SOCKET_DISCONNECT) {
            handle->milsec= 10000000;
            ul = 1;
            fibo_sock_lwip_fcntl(handle->fd, FIONBIO, &ul); //
            handle->nfsm = SOCKET_CONNECTSTART;
            fibo_sock_connect(handle->fd, &serv_addr);
        } 

    } else if (handle->type == SOCKET_TYPE_NOBLOCK) {
    	//QXLOGI("qxwz_sock_connect non-block \n");

        if (handle->nfsm == SOCKET_DISCONNECT) {
			//QXLOGI("qxwz_sock_connect non-block test0 \n");
            fibo_sock_lwip_fcntl(handle->fd, F_SETFL, fibo_sock_lwip_fcntl( handle->fd, F_GETFL, 0 ) | O_NONBLOCK ); //
            //QXLOGI("qxwz_sock_connect non-block test1 \n");
            /* connect the socket */
            result = fibo_sock_connect(handle->fd, &serv_addr);
            if (result == 0) {
				QXLOGI("qxwz_sock_connect non-block connect ok \n");
                handle->conn_status = 0;
            } else {
				QXLOGI("qxwz_sock_connect non-block connect not ok errno=%d  \n",errno);
                if (errno == EINPROGRESS) {
					QXLOGI("qxwz_sock_connect non-block ongoing \n");
                    handle->conn_status = 1;
               } else {
			   		QXLOGI("qxwz_sock_connect non-block fail \n");
                    handle->conn_status = -1;
                }
            }
			//QXLOGI("qxwz_sock_connect non-block test6 \n");
            handle->nfsm = SOCKET_CONNECTING;
        }
    }
	QXLOGI("qxwz_sock_connect return con_status %d,nfsm=%d \n",handle->conn_status,handle->nfsm);

    return handle->conn_status;
}

static void _qxwz_socket_poll(QxwzSocketStruct *handle, void(* connect_cb)(void *), void(* recv_cb)(void *))
{ 
    int timer_ret;
	QXLOGI("_qxwz_socket_poll enter\n");
    if (handle->type == SOCKET_TYPE_BLOCK) {
		QXLOGI("_qxwz_socket_poll test0 \n");

        if (handle->nfsm == SOCKET_CONNECTSTART) {
            //timer_ret = qxwz_timer_create(handle->priv,handle->name, (qxwz_timer_callback_t)socket_check_connect,0,(void *)handle);
            //qxwz_timer_start((QxwzTimerHandle)handle->priv, 10);
            //qxwz_timer_schedule((qxwz_timer_t)handle->priv, 10,0);
            handle->nfsm = SOCKET_CONNECTING;
			QXLOGI("_qxwz_socket_poll test1 \n");
        } else if (handle->nfsm == SOCKET_CONNECTING) {
			QXLOGI("_qxwz_socket_poll test2 \n");
            if (connect_cb)
                connect_cb(handle);
        } else if (handle->nfsm == SOCKET_CONNECTED) {
        	QXLOGI("_qxwz_socket_poll test3 \n");
            //qxwz_timer_delete(handle->priv);
            handle->rfsm = SOCKET_READSTART;
            handle->nfsm = SOCKET_CONNECTED2;
            if (connect_cb)
                connect_cb(handle);
        } else if (handle->nfsm == SOCKET_CONNECTERR){
        	QXLOGI("_qxwz_socket_poll test4 \n");
            //qxwz_timer_delete(handle->priv);
            if (connect_cb)
                connect_cb(handle);
            QXLOGI("block connect timeout...\n");
        }

        if (handle->nfsm != SOCKET_CONNECTED && handle->nfsm != SOCKET_CONNECTED2) {
			QXLOGI("_qxwz_socket_poll test5 \n");
            return;
        }

        if (handle->rfsm == SOCKET_READSTART) {
			QXLOGI("_qxwz_socket_poll test6 \n");
             //timer_ret = qxwz_timer_create(handle->priv,handle->name, (qxwz_timer_callback_t)socket_check_read, 0,(void *)handle);
            //qxwz_timer_start((QxwzTimerHandle)handle->priv, 10);
            //qxwz_timer_schedule((qxwz_timer_t)handle->priv, 10,0);
            handle->rfsm = SOCKET_READING;
        } else if (handle->rfsm == SOCKET_READING) {
			QXLOGI("_qxwz_socket_poll test7 \n");
            if (handle->read_status == 0)
                handle->rfsm = SOCKET_READED;
            else
                handle->rfsm = SOCKET_READERR;
        } else if (handle->rfsm == SOCKET_READED) {
        	QXLOGI("_qxwz_socket_poll test8 \n");
            //qxwz_timer_delete(handle->priv);
            handle->rfsm = SOCKET_READSTOP;
        } else if (handle->rfsm == SOCKET_READERR) {
			QXLOGI("_qxwz_socket_poll test9 \n");
            //qxwz_timer_delete(handle->priv);
            handle->rfsm = SOCKET_READSTOP;
        } else if (handle->rfsm == SOCKET_READSTOP) {
			QXLOGI("_qxwz_socket_poll test10 \n");
            handle->rfsm = SOCKET_READSTART;
        }

        if (handle->read_status == -1) {
            if (recv_cb)
                recv_cb(handle);
        } else if (handle->read_status == 0) {
            if (recv_cb)
                recv_cb(handle);
        } else if (handle->read_status == 1) {

        }


    
    } else if (handle->type == SOCKET_TYPE_NOBLOCK) {
    	//QXLOGI("_qxwz_socket_poll test0 \n");
        if (handle->nfsm == SOCKET_DISCONNECT) {
			//QXLOGI("_qxwz_socket_poll test1 \n");
            return;
        } else if (handle->nfsm == SOCKET_CONNECTSTART) {
			//QXLOGI("_qxwz_socket_poll test2 \n");
            return;
        } else if (handle->nfsm == SOCKET_CONNECTERR) {
			//QXLOGI("_qxwz_socket_poll test3 \n");
            if (connect_cb)
                connect_cb(handle);
            return;
        } else if (handle->nfsm == SOCKET_CONNECTING) {
			//QXLOGI("_qxwz_socket_poll test5 \n");
            if (connect_cb)
                connect_cb(handle);
            handle->milsec = 30000;
            socket_check_connect(handle);
            if (handle->conn_status == 0) {
				//QXLOGI("_qxwz_socket_poll test8 \n");
                handle->nfsm = SOCKET_CONNECTED;
            }
        } else if (handle->nfsm == SOCKET_CONNECTED) {
        	//QXLOGI("_qxwz_socket_poll test6 \n");
            handle->nfsm = SOCKET_CONNECTED2;
            if (connect_cb)
                connect_cb(handle);
        } else  if (handle->nfsm == SOCKET_CONNECTED2) {
        	//QXLOGI("_qxwz_socket_poll test7 \n");
			handle->milsec = 500;
        	socket_check_read(handle);
	        if (handle->read_status == -1) {
	            if (recv_cb)
	                recv_cb(handle);
	        } else if (handle->read_status == 0) {
	            if (recv_cb)
	                recv_cb(handle);
	        } else if (handle->read_status == 1) {

	        }

        }

    }
	//QXLOGI("_qxwz_socket_poll return \n");
}

/*
 * @brief: polling socket , support block or no-block operation
 * @param[in] handle: a socket pointer
 * @param[in] n: number of QxwzSocketStruct
 *
 * @return: none
 */
void qxwz_sock_polling(QxwzSocketStruct *handle, int n, void(* connect_cb)(void *), void(* recv_cb)(void *))
{
    int i = 0;

    for (i = 0; i < n; i++) {
        if (handle[i].fd <= 0) {
            handle[i].nfsm = SOCKET_DISCONNECT;
            handle[i].rfsm = SOCKET_READSTOP;
            continue;
        }
        _qxwz_socket_poll(&handle[i], connect_cb, recv_cb);
    }

}



